# -*- coding: utf-8 -*-
import random

import numpy as np

from qa_diagnosis.neoapp import query_detailed_disease_list_by_symptom

decay_weight = 1.0

disease_desc_dict = {
    "支气管扩张": "支气管扩张症是由于支气管及其周围肺组织慢性化脓性炎症和纤维化，使支气管壁的肌肉和弹性组织破坏，导致支气管变形及持久扩张。典型的症状有慢性咳嗽、咳大量脓痰和反复咯血。主要致病因素为支气管感染、阻塞和牵拉，部分有先天遗传因素。患者多有麻疹、百日咳或支气管肺炎等病史。",
    "气胸": "气胸是指气体进入胸膜腔，造成积气状态，称为气胸。多因肺部疾病或外力影响使肺组织和脏层胸膜破裂，或靠近肺表面的细微气肿泡破裂，肺和支气管内空气逸入胸膜腔。因胸壁或肺部创伤引起者称为创伤性气胸；因疾病致肺组织自行破裂引起者称“自发性气胸”，如因治疗或诊断所需人为地将空气注入胸膜腔称“人工气胸”。气胸又可分为闭合性气胸、开放性气胸及张力性气胸。自发性气胸多见于男性青壮年或患有慢支、肺气肿、肺结核者。本病属肺科急症之一，严重者可危及生命，及时处理可治愈。",
    "肺结核": "结核病是由结核分枝杆菌引起的慢性传染病，可侵及许多脏器，以肺部结核感染最为常见。排菌者为其重要的传染源。人体感染结核菌后不一定发病，当抵抗力降低或细胞介导的变态反应增高时，才可能引起临床发病。若能及时诊断，并予合理治疗，大多可获临床痊愈。",
    "胸腔积液": "胸腔积液是以胸膜腔内病理性液体积聚为特征的一种常见临床症候。胸膜腔为脏层和壁层胸膜之间的一个潜在间隙，正常人胸膜腔内有5～15ml液体，在呼吸运动时起润滑作用，胸膜腔内每天有500～1000ml的液体形成与吸收，任何原因导致胸膜腔内液体产生增多或吸收减少，即可产生胸腔积液。按其发生机制可分为漏出性胸腔积液和渗出性胸腔积液两类。",
    "肺血栓栓塞症": "肺血栓栓塞症就是，肺动脉被栓塞住，栓塞的栓子是血栓，就是体内静脉比如下肢静脉，也有少部分是上肢静脉，出现了血栓。血栓脱落以后，顺着血管壁、静脉往上走，会走到右心，这个过程是没有阻碍的，到右心以后再把血打到肺里去。肺血管是由粗变细，可能会卡顿在肺血管里。如果卡顿在大动脉里会比较危险，病情比较危重，甚至有猝死的风险。如果血栓相对来说比较小，栓在远端血管里也会引起一定程度的呼吸困难，甚至有小部分咯血。这时候取决于它栓塞的面积，如果栓塞的肺面积比较大，影响到呼吸功能，也会出现缺氧、咯血症状。如果出现这种情况，特别是有下肢静脉曲张的患者，突然出现胸闷、气短，需要到医院就诊。因为肺血栓栓塞症的治疗是靠溶栓或者抗炎，这两种治疗方法都是有时间窗的，时间越晚，疗效越差，对药物的反应也差。",
    "原发性支气管肺癌": "原发性支气管肺癌即是肺癌。肺癌是发病率和死亡率增长最快，对人群健康和生命威胁最大的恶性肿瘤之一。近50年来许多国家都报道肺癌的发病率和死亡率均明显增高，男性肺癌发病率和死亡率均占所有恶性肿瘤的第一位，女性发病率占第二位，死亡率占第二位。肺癌的病因至今尚不完全明确，大量资料表明，长期大量吸烟与肺癌的发生有非常密切的关系。已有的研究证明长期大量吸烟者患肺癌的概率是不吸烟者的10～20倍，开始吸烟的年龄越小，患肺癌的几率越高。此外吸烟还会对周围人群的健康产生不良影响，导致被动吸烟者患肺癌的发生率也明显增加。",
    "慢性支气管炎": "慢性支气管炎是气管、支气管黏膜及周围组织的慢性非特异性炎症。临床以咳嗽、咳痰为主要症状，每年发病持续3个月，连续2年或2年以上。需要进一步排除具有咳嗽、咳痰、喘息症状的其他疾病（如肺结核、尘肺、肺脓肿、心脏病、心功能不全、支气管扩张、支气管哮喘、慢性鼻咽炎、食管反流综合征等疾患）。",
    "支气管哮喘": "支气管哮喘是由多种细胞（如嗜酸性粒细胞、肥大细胞、T淋巴细胞、中性粒细胞、气道上皮细胞等）和细胞组分参与的气道慢性炎症为特征的异质性疾病，这种慢性炎症与气道高反应性相关，通常出现广泛而多变的可逆性呼气气流受限，导致反复发作的喘息、气促、胸闷和（或）咳嗽等症状，强度随时间变化。多在夜间和（或）清晨发作、加剧，多数患者可自行缓解或经治疗缓解。支气管哮喘如诊治不及时，随病程的延长可产生气道不可逆性缩窄和气道重塑。"
}


class Diagnosis:

    def __init__(self):
        self.disease_name_list = None
        # 此项为 numpy 数组, shape 为 (1, 2)
        self.disease_freq_list = None
        self.detailed_disease_list = None
        self.inquiry_list = None
        self.latest_inquired_symptom = None
        self.latest_inquired_property = None
        self.latest_submit_option = None
        self.max_likelihood_symptom = None
        self.max_likelihood_property = None
        self.max_likelihood_option_list = None
        self.question = None

    def entrance(self,
                 disease_name_list,
                 disease_freq_list,
                 detailed_disease_list,
                 inquiry_list):
        """
        入口程序
        """
        self.inquiry_list = inquiry_list

        latest_inquiry_list = inquiry_list[-1]
        self.latest_inquired_symptom = latest_inquiry_list[0]
        self.latest_inquired_property = latest_inquiry_list[1]
        self.latest_submit_option = latest_inquiry_list[2]

        if blank_verify(self.latest_inquired_property) is None:
            # 初始化参数列表
            self.init_prob_list()
        else:
            self.disease_name_list = disease_name_list
            self.disease_freq_list = disease_freq_list
            self.detailed_disease_list = detailed_disease_list

        # 根据用户提交的选项的进行概率计算，即匹配的疾病概率进行增益，不匹配的疾病概率进行削减
        self.prob_decay_or_increase()
        # 删除已经提问过的两个候选表
        self.clear_details()
        if len(self.detailed_disease_list) == 0:
            self.question = '请问您还有其他症状么？'
            self.max_likelihood_symptom = None
            self.max_likelihood_property = None
            self.max_likelihood_option_list = ['有', '没有']
        else:
            # 继续询问用户概率最大的那个选项
            self.get_max_likelihood_inquiry()

            question = [f'您出现{self.max_likelihood_symptom}的{self.max_likelihood_property}是以下哪种类型？',
                        f'请问，您{self.max_likelihood_symptom}的{self.max_likelihood_property}是以下哪种？',
                        f'您{self.max_likelihood_symptom}的{self.max_likelihood_property}是哪种情况？']
            random.shuffle(question)
            self.question = question[0]
        self.disease_freq_list = self.disease_freq_list.tolist()

    def get_max_likelihood_disease(self):
        disease_freq_list = self.disease_freq_list
        disease_name_list = self.disease_name_list

        disease_freq_list = disease_freq_list.reshape(1, -1)
        max_freq_index = np.argmax(disease_freq_list, axis=1)[0]
        max_likelihood_disease = disease_name_list[max_freq_index]
        return max_likelihood_disease

    def get_detailed_disease_list(self):
        detailed_disease_list = self.detailed_disease_list
        return detailed_disease_list

    def get_max_likelihood_inquiry(self):

        disease_name_list = self.disease_name_list

        detailed_disease_list = self.get_detailed_disease_list()

        disease_param_max_sum_list = [0] * len(disease_name_list)
        disease_param_max_index_list = [0] * len(disease_name_list)

        for index, detailed_disease_item in enumerate(detailed_disease_list):

            disease = detailed_disease_item['disease']
            param_sum = self.get_param_sum(detailed_disease_item)

            cur_disease_index = disease_name_list.index(disease)
            if param_sum > disease_param_max_sum_list[cur_disease_index]:
                disease_param_max_sum_list[cur_disease_index] = param_sum
                disease_param_max_index_list[cur_disease_index] = index

        max_likelihood_disease = self.get_max_likelihood_disease()
        disease_index = disease_name_list.index(max_likelihood_disease)

        max_likelihood_index = disease_param_max_index_list[disease_index]
        max_likelihood_dict = detailed_disease_list[max_likelihood_index]

        max_likelihood_symptom = max_likelihood_dict['symptom']
        max_likelihood_property = max_likelihood_dict['property']
        self.inquiry_symptom_property(max_likelihood_symptom,
                                      max_likelihood_property,
                                      detailed_disease_list)

    def is_symptom_first_inquired(self, symptom):
        inquired_symptom_property_list = self.get_inquired_symptom_property_list()
        for inquired_item in inquired_symptom_property_list:
            if inquired_item['symptom'] == symptom:
                break
        else:
            return True
        return False

    def inquiry_symptom_property(self, max_likelihood_symptom,
                                 max_likelihood_property,
                                 detailed_disease_list):
        max_likelihood_option_list = []

        if self.is_symptom_first_inquired(max_likelihood_symptom) is True:
            max_likelihood_property = None
            max_likelihood_option_list.append('是')
            max_likelihood_option_list.append('否')
        else:
            for detailed_disease_item in detailed_disease_list:
                if detailed_disease_item['symptom'] == max_likelihood_symptom:
                    if detailed_disease_item['property'] == max_likelihood_property:
                        option = detailed_disease_item['option']
                        if option not in max_likelihood_option_list:
                            max_likelihood_option_list.append(option)
            max_likelihood_option_list.append('以上都不是')

        self.max_likelihood_symptom = max_likelihood_symptom
        self.max_likelihood_property = max_likelihood_property
        self.max_likelihood_option_list = max_likelihood_option_list

    def init_prob_list(self):
        latest_inquired_symptom = self.latest_inquired_symptom
        detailed_disease_list = \
            query_detailed_disease_list_by_symptom(latest_inquired_symptom)

        disease_name_list = []
        disease_freq_list = []

        disease_count_list = []

        for index, detailed_disease_item in enumerate(detailed_disease_list):
            disease = detailed_disease_item['disease']
            freq = float(detailed_disease_item['freq'])

            if disease not in disease_name_list:
                disease_name_list.append(disease)
                disease_freq_list.append(0.1 + freq)
                disease_count_list.append(1)
            else:
                cur_index = disease_name_list.index(disease)
                disease_freq_list[cur_index] += freq
                disease_count_list[cur_index] += 1

        disease_count_list = np.array(disease_count_list)
        disease_freq_list = np.array(disease_freq_list)
        disease_freq_list /= disease_count_list
        disease_freq_list /= np.sum(disease_freq_list)

        self.disease_name_list = disease_name_list
        self.disease_freq_list = disease_freq_list.reshape(-1)
        self.detailed_disease_list = detailed_disease_list

    def prob_calculation_by_prominent_disease(self, option):
        detailed_disease_list = self.detailed_disease_list
        latest_inquired_symptom = self.latest_inquired_symptom
        latest_inquired_property = self.latest_inquired_property
        disease_param_max_sum = 0
        prominent_detailed_item = None

        for index, detailed_disease_item in enumerate(detailed_disease_list):
            freq = float(detailed_disease_item['freq'])
            impt = float(detailed_disease_item['impt'])
            spec = float(detailed_disease_item['spec'])
            param_sum = freq + impt + spec
            if param_sum > disease_param_max_sum:
                disease_param_max_sum = param_sum
                prominent_detailed_item = detailed_disease_item

        if latest_inquired_symptom != prominent_detailed_item['symptom']:
            self.max_likelihood_symptom = prominent_detailed_item['symptom']
            self.max_likelihood_property = None
            self.max_likelihood_option_list = ['是', '否']
        else:
            if blank_verify(latest_inquired_property) is None:
                if option == '是':
                    self.inquiry_symptom_property(prominent_detailed_item['symptom'],
                                                  prominent_detailed_item['property'],
                                                  detailed_disease_list=None)
                elif option == '否':
                    self.prob_decay_by_symptom(detailed_disease_list)
                    # 此症状已经全部问完
            else:
                print('对该属性进行概率削减')

    def get_inquired_symptom_property_list(self):
        inquired_symptom_property_list = []
        for inquiry_item in self.inquiry_list:
            symptom = inquiry_item[0]
            property = inquiry_item[1]
            if property is None:
                inquired_symptom_property_list.append({'symptom': symptom, 'property': None})
                continue
            for inquired_symptom_property_item in inquired_symptom_property_list:
                if inquired_symptom_property_item['symptom'] == symptom and \
                        inquired_symptom_property_item['property'] == property:
                    break
            else:
                inquired_symptom_property_list.append({'symptom': symptom, 'property': property})
        return inquired_symptom_property_list

    @staticmethod
    def get_param_sum(detailed_disease_item):
        freq = float(detailed_disease_item['freq'])
        impt = float(detailed_disease_item['impt'])
        spec = float(detailed_disease_item['spec'])
        param_sum = freq + impt + spec
        return param_sum

    def get_disease_freq(self, disease):
        disease_name_list = self.disease_name_list
        disease_freq_list = self.disease_freq_list
        return disease_freq_list[disease_name_list.index(disease)]

    def clear_details(self):
        detailed_disease_list = self.detailed_disease_list

        new_detailed_disease_list = []

        inquired_symptom_property_list = self.get_inquired_symptom_property_list()
        if detailed_disease_list is not None:
            for detailed_item in detailed_disease_list:
                for inquired_item in inquired_symptom_property_list:
                    if detailed_item['symptom'] == inquired_item['symptom'] \
                            and detailed_item['property'] == inquired_item['property']:
                        break
                else:
                    new_detailed_disease_list.append(detailed_item)
            self.detailed_disease_list = new_detailed_disease_list

    def prob_decay_or_increase(self):
        disease_name_list = self.disease_name_list
        disease_freq_list = self.disease_freq_list

        detailed_disease_list = self.get_detailed_disease_list()

        latest_inquired_symptom = self.latest_inquired_symptom
        latest_inquired_property = self.latest_inquired_property
        option = self.latest_submit_option

        for index, detailed_disease_item in enumerate(detailed_disease_list):
            if detailed_disease_item['symptom'] == latest_inquired_symptom:
                if detailed_disease_item['property'] == latest_inquired_property:
                    param_sum = self.get_param_sum(detailed_disease_item)
                    if option != '以上都不是':
                        if detailed_disease_item['option'] == option:
                            # 匹配的疾病概率提升
                            disease = detailed_disease_item['disease']
                            cur_disease_index = disease_name_list.index(disease)
                            disease_freq_list[cur_disease_index] += 1 + param_sum / 15 * decay_weight
                        else:
                            # 不匹配的疾病概率降低
                            disease = detailed_disease_item['disease']
                            cur_disease_index = disease_name_list.index(disease)
                            disease_freq_list[cur_disease_index] += 1 - param_sum / 15 * decay_weight
                    else:
                        # 不匹配的疾病概率降低
                        disease = detailed_disease_item['disease']
                        cur_disease_index = disease_name_list.index(disease)
                        disease_freq_list[cur_disease_index] += 1 - param_sum / 15 * decay_weight
        self.disease_freq_list = disease_freq_list.reshape(-1)

    def prob_decay_by_symptom(self, detailed_disease_list):
        disease_name_list = self.disease_name_list
        disease_freq_list = self.disease_freq_list

        latest_inquired_symptom = self.latest_inquired_symptom

        for index, detailed_disease_item in enumerate(detailed_disease_list):
            if detailed_disease_item['symptom'] == latest_inquired_symptom:
                impt = float(detailed_disease_item['impt'])
                spec = float(detailed_disease_item['spec'])
                score = impt + spec
                # 不匹配的疾病概率降低
                disease = detailed_disease_item['disease']
                cur_disease_index = disease_name_list.index(disease)
                disease_freq_list[cur_disease_index] += 1 - score / 10 * decay_weight

        self.disease_freq_list = disease_freq_list.reshape(-1)


def my_test():
    print('您好，我是您的智能问诊助手，请问您有什么症状需要提问呀？')
    inquiry_list = []
    app = Diagnosis()

    while (1):

        if app.max_likelihood_symptom is None:
            symptom = input()
            if symptom == "没有其他症状":
                break
            inquiry_list.append([symptom, None, '是'])
        else:
            option_selected = input()
            inquiry_list.append([app.max_likelihood_symptom, app.max_likelihood_property, option_selected])

        count = inquiry_list_count(inquiry_list)
        if count >= 10:
            break

        app.entrance(disease_name_list=app.disease_name_list,
                     disease_freq_list=app.disease_freq_list,
                     detailed_disease_list=app.detailed_disease_list,
                     inquiry_list=inquiry_list)
        print(app.inquiry_list)
        print('-' * 100)
        print(get_top_k(app.disease_name_list, app.disease_freq_list))
        print(app.question)
        print(f'{app.max_likelihood_option_list}')
        print('-' * 50)


def inquiry_list_count(inquiry_list):
    count = 0
    for i in inquiry_list:
        if i[1] is None:
            continue
        else:
            count += 1
    return count


def get_top_k(disease_name_list, disease_freq_list, k=3):
    def proportion(freq):
        return freq / np.sum(freq)

    disease_freq_list = proportion(disease_freq_list)

    top_k = dict(zip(disease_name_list, disease_freq_list))
    sorted_top_k = sorted(top_k.items(), key=lambda x: x[1], reverse=True)

    # convert list of tuples to list of list
    sorted_top_k = [list(ele) for ele in sorted_top_k]
    sorted_top_k = sorted_top_k[0:k]

    results = []
    for ele in sorted_top_k:
        results.append({'disease_name': ele[0], 'freq': ele[1], 'desc': disease_desc_dict[ele[0]]})
    return results


def blank_verify(my_str):
    if not my_str or (my_str and not my_str.strip()):
        return None


if __name__ == '__main__':
    my_test()
